﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.DirectoryServices;
using System.IO;
using System.Security.Principal;
using System.Web.Security;

namespace BlogEngine.Contrib.Providers
{
    class ADRoleProvider : RoleProvider
    {
        #region Class Properties

        private NameValueCollection _Config;
        private String _ApplicationName;
        private string _RGConfigPath = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/RoleGroupConfig.config");

        private string _AdministratorRoleName = String.Empty;
        public string AdministratorRoleName
        {
            get
            {
                if (String.IsNullOrEmpty(_AdministratorRoleName))
                {
                    if (!String.IsNullOrEmpty(ConfigurationManager.AppSettings["BlogEngine.AdminRole"]))
                    {
                        _AdministratorRoleName = ConfigurationManager.AppSettings["BlogEngine.AdminRole"];
                    }
                    else
                    {
                        _AdministratorRoleName = "Administrators";
                    }
                }
                return _AdministratorRoleName;
            }

        }

        private string _EditorRoleName = String.Empty;
        public string EditorRoleName
        {
            get
            {
                if (String.IsNullOrEmpty(_EditorRoleName))
                {
                    if (!String.IsNullOrEmpty(ConfigurationManager.AppSettings["BlogEngine.EditorRole"]))
                    {
                        _EditorRoleName = ConfigurationManager.AppSettings["BlogEngine.EditorRole"];
                    }
                    else if (!String.IsNullOrEmpty(_Config["editorRoleName"]))
                    {
                        _EditorRoleName = _Config["editorRoleName"];
                    }
                    else
                    {
                        _EditorRoleName = "Editors";
                    }
                }
                return _EditorRoleName;
            }
        }

        private string _AdministratorGroupName = String.Empty;
        public string AdministratorGroupName
        {
            get
            {
                if (String.IsNullOrEmpty(_AdministratorGroupName))
                {
                    if (!String.IsNullOrEmpty(_Config["administratorGroupName"]))
                    {
                        _AdministratorGroupName = _Config["administratorGroupName"];
                    }
                    else
                    {
                        _AdministratorGroupName = "Blogadmins";
                    }
                }
                return _AdministratorGroupName;
            }
        }

        public string _EditorGroupName = String.Empty;
        public string EditorGroupName
        {
            get
            {
                if (String.IsNullOrEmpty(_EditorGroupName))
                {
                    if (!String.IsNullOrEmpty(_Config["editorGroupName"]))
                    {
                        _EditorGroupName = _Config["editorGroupName"];
                    }
                    else
                    {
                        _EditorGroupName = "Blogeditors";
                    }
                }
                return _EditorGroupName;
            }
        }

        private RoleGroupConfig _RGConfig;
        public RoleGroupConfig RGConfig
        {
            get
            {
                if (_RGConfig == null)
                {
                    _RGConfig = RoleGroupConfig.Open(_RGConfigPath);

                    if (!File.Exists(_RGConfigPath))
                    {
                        _RGConfig.addMapping(AdministratorRoleName, AdministratorGroupName);
                        _RGConfig.addMapping(EditorRoleName, EditorGroupName);
                        _RGConfig.Save(_RGConfigPath);
                    }
                }
                return _RGConfig;
            }
        }

        private string _adUserName = String.Empty;
        public string ADUsername
        {
            get
            {
                if (_adUserName == String.Empty)
                {

                    if (_Config["adUserName"] != null)
                    {
                        _adUserName = _Config["adUserName"];
                    }
                    else
                    {
                        throw new ConfigurationException("Active Directory Roles Provider is not properly configured. You must configure the adUserName attribute on your roles provider node in web.config.");
                    }
                }
                return _adUserName;
            }
        }

        private string _adPassword = String.Empty;
        public string ADPassword
        {
            get
            {
                if (_adPassword == String.Empty)
                {
                    if (_Config["adPassword"] != null)
                    {
                        _adPassword = _Config["adPassword"];
                    }
                    else
                    {
                        throw new ConfigurationException("Active Directory Roles Provider is not properly configured. You must configure the adPassword attribute on your roles provider node in web.config.");
                    }
                }
                return _adPassword;
            }
        }

        private string _adRootPath = String.Empty;
        public string ADRootPath
        {
            get
            {
                if (_adRootPath == String.Empty)
                {
                    if (_Config["adRootPath"] != null)
                    {
                        _adRootPath = _Config["adRootPath"];
                    }
                    else
                    {
                        throw new ConfigurationException("Active Directory Roles Provider is not properly configured. You must configure the adRootPath attribute on your roles provider node in web.config.");
                    }
                }
                return _adRootPath;
            }
        }

        #endregion

        #region RoleProvider Implementation

        public override void Initialize(String name, NameValueCollection config)
        {
            _Config = config;
            _ApplicationName = config["ApplicationName"];
            base.Initialize(name, config);
        }

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            foreach (String role in roleNames)
            {
                // get directory entry for group
                DirectoryEntry group = getGroup(RGConfig.getGroup(role));

                foreach (string username in usernames)
                {
                    // get directory entry for user
                    DirectoryEntry user = getUser(username);

                    // add user's distinguished name to members list of the group
                    group.Properties["member"].Add(user.Properties["distinguishedName"].Value);

                    // close up the user DE
                    user.Close();
                }

                // send the changes to AD
                group.CommitChanges();

                // close up the group DE
                group.Close();
            }
        }

        public override string ApplicationName
        {
            get
            {
                return _ApplicationName;
            }
            set
            {
                _ApplicationName = value;
            }
        }

        public override void CreateRole(string roleName)
        {
            throw new NotImplementedException();
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            throw new NotImplementedException();
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            throw new NotImplementedException();
        }

        public override string[] GetAllRoles()
        {
            return RGConfig.Roles.ToArray();
        }

        public override string[] GetRolesForUser(string username)
        {
            // get the user directory entry
            DirectoryEntry user = getUser(username);

            // list to store names of roles
            List<String> roles = new List<string>();


            IdentityReferenceCollection groupCollection = ExpandTokenGroups(user).Translate(typeof(NTAccount));

            foreach (NTAccount group in groupCollection)
            {
                String roleName = RGConfig.getRole(group.ToString().Substring(group.ToString().IndexOf(@"\") + 1));
                if (roleName != String.Empty)
                {
                    roles.Add(roleName);
                }
            }

            return roles.ToArray();
        }

        public override string[] GetUsersInRole(string roleName)
        {
            //return GroupMembers[roleName].ToArray();
            throw new NotImplementedException();
        }

        public override bool IsUserInRole(string username, string roleName)
        {

            string[] roles = this.GetRolesForUser(username);

            foreach (string role in roles)
            {
                if (role == roleName)
                {
                    return true;
                }
            }

            return false;
        }


        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            foreach (String role in roleNames)
            {
                // get directory entry for the group
                DirectoryEntry group = getGroup(RGConfig.getGroup(role));

                foreach (String username in usernames)
                {
                    // get directory entry for user
                    DirectoryEntry user = getUser(username);

                    // remove user's distinguished name from group's member list
                    group.Properties["member"].Remove(user.Properties["distinguishedName"].Value);

                    // close user DE
                }

                // send changes to AD
                group.CommitChanges();

                // close group DE
                group.Close();
            }
        }

        public override bool RoleExists(string roleName)
        {
            return _RGConfig.Roles.Contains(roleName);
        }

        #endregion

        #region Private Methods

        private DirectoryEntry getGroup(string groupName)
        {
            DirectoryEntry root = getRoot();
            DirectorySearcher ds = new DirectorySearcher(root);
            ds.Filter = "(&(objectClass=group)(cn=" + groupName + "))";
            SearchResultCollection groups = ds.FindAll();
            if (groups.Count > 0)
            {
                return groups[0].GetDirectoryEntry();
            }
            return null;
        }

        private DirectoryEntry getUser(string userName)
        {
            DirectoryEntry root = getRoot();
            DirectorySearcher ds = new DirectorySearcher(root);
            ds.Filter = "(&(objectClass=user)(sAMAccountName=" + userName + "))";
            SearchResultCollection users = ds.FindAll();
            if (users.Count > 0)
            {
                return users[0].GetDirectoryEntry();
            }
            return null;
        }

        private DirectoryEntry getRoot()
        {
            return new DirectoryEntry(ADRootPath, ADUsername, ADPassword, AuthenticationTypes.ServerBind);
        }

        private IdentityReferenceCollection ExpandTokenGroups(DirectoryEntry user)
        {
            user.RefreshCache(new string[] { "tokenGroups" });

            IdentityReferenceCollection irc =
              new IdentityReferenceCollection();

            foreach (byte[] sidBytes in user.Properties["tokenGroups"])
            {
                irc.Add(new SecurityIdentifier(sidBytes, 0));
            }
            return irc;
        }

        #endregion
    }
}

